#include "event_logger.h"
#include "../base/debug.h"
#include "../base/error.h"

void LogEventHelper(WORD type, DWORD id, size_t count, 
					const TCHAR** strings, const TCHAR* ctx)
{
	ASSERT1(count <= kint16max);
	if (!ConfigManager::Instance()->CanLogEvents(type))
		return;

	// Include the circular logging buffer in the event log if the type is a
	// warning or an error.
	CStringA data(ctx);
	CString context = GetLogging()->GetHistory();
	if (!context.IsEmpty())
		SafeCStringAAppendFormat(&data, "\n[More context: %s]", context);

	HRESULT hr = EventLogger::ReportEvent(EventLogger::kSourceName, type, EventLogger::kDefaultCategory,
		id, static_cast<WORD>(count), strings, data.GetLength(), data.GetBuffer());
	if (FAILED(hr))
		CORE_LOG(LW, (_T("[Failed to log event][0x%08x]"), hr));
}

CString BuildEventSourceRegistryKeyName(const TCHAR* src_name)
{
	ASSERT1(src_name);
	CString key_name;
	SafeCStringFormat(&key_name, _T("HKLM\\SYSTEM\\CurrentControlSet\\Services\\EventLog\\")
		_T("Application\\%s"), src_name);
	return key_name;
}

HRESULT EventLogger::AddEventSource(const TCHAR* src_name, 
									const TCHAR* msg_dll_path)
{
	ASSERT1(src_name);
	if (!src_name) return E_INVALIDARG;
	ASSERT1(msg_dll_path);
	if (!msg_dll_path) return E_INVALIDARG;

	RegKey reg_key;
	HRESULT hr = reg_key.Create(BuildEventSourceRegistryKeyName(src_name));
	if (FAILED(hr)) return hr;

	// Set the name of the message file. RegKey class can't set REG_EXPAND_SZ
	// values so we must use the low level OS call.
	int result = ::RegSetValueEx(reg_key.Key(), _T("EventMessageFile"), 0, REG_EXPAND_SZ,
		reinterpret_cast<const byte*>(msg_dll_path), (_tcslen(msg_dll_path) + 1) * sizeof(TCHAR));
	if (result != ERROR_SUCCESS) return HRESULT_FROM_WIN32(result);

	// Set the supported event types.
	DWORD types = EVENTLOG_ERROR_TYPE |
		EVENTLOG_WARNING_TYPE|
		EVENTLOG_INFORMATION_TYPE;
	hr = reg_key.SetValue(_T("TypesSupported"), types);

	return FAILED(hr) ? hr : S_OK;
}

HRESULT EventLogger::RemoveEventSource(const TCHAR* src_name)
{
	ASSERT1(src_name);
	if (!src_name) return E_INVALIDARG;

	HRESULT hr = RegKey::DeleteKey(BuildEventSourceRegistryKeyName(src_name),
		false);
	return SUCCEED(hr) ? S_OK : hr;s
}

HRESULT EventLogger::ReportEvent(const TCHAR* src_name,
								 WORD type,
								 WORD category,
								 DWORD id,
								 WORD count,
								 const TCHAR** strings,
								 size_t buf_size,
								 void* buffer)
{
	ASSERT1(src_name);
	ASSERT1(type == EVENTLOG_SUCCESS ||
		type == EVENTLOG_ERROR_TYPE ||
		type == EVENTLOG_WARNING_TYPE ||
		type == EVENTLOG_INFORMATION_TYPE);

	HANDLE hlog = ::RegisterEventSource(NULL, src_name);
	if (!hlog) return HRESULTFromLastError();

	// Best effort to get the sid for the current effective user. The event
	// logging provides for logging the sid at no cost so that the user shows up
	// in the event log.
	CString sid_string;
	VERIFY1(SUCCEEDED(user_info::GetEffectiveUserSid(&sid_string)));
	PSID psid = NULL;
	if (!sid_string.IsEmpty())
	{
		VERIFY1(::ConvertStringSidToSid(sid_string, &psid));
		ASSERT1(psid);
	}

	HRESULT hr = E_FAIL;
	if (::ReportEvent(hlog, type, category, id, psid, count, buf_size, strings, buffer))
		hr = S_OK;
	else
		hr = HRESULTFromLastError();

	::LocalFree(psid);
	VERIFY1(::DeregisterEventSource(hlog));
	return hr;
}

HRESULT EventLogger::ReadLastEvent(const TCHAR* src_name, EVENTLOGRECORD* rec)
{
	if (!(rec && src_name)) return E_INVALIDARG;

	HANDLE hlog = ::OpenEventLog(NULL, src_name);
	if (!hlog) return HRESULTFromLastError();

	HRESULT hr = E_FAIL;
	DWORD bytes_read(0), bytes_needed(0);
	const DWORD read_flags = EVENTLOG_BACKWARDS_READ | EVENTLOG_SEQUENTIAL_READ;
	if (::ReadEventLog(hlog, read_flags, 0, rec, rec->Length, &bytes_needed, &bytes_needed))
		hr = S_OK;
	else
		hr = HRESULTFromLastError();

	::CloseEventLog(hlog);
	return hr;
}

// TODO: Decide whether to use IDS_PRODUCT_DISPLAY_NAME instead.
// On one hand this string makes it to the event viewer, on
// the other hand the same string is used to register an event log for an
// application in registry. We do not expect the mapping to change when the user
// changes languages or there are multiple users for a per-machine install that
// are using different languages., however we may decide to do so.
const TCHAR* const EventLogger::kSourceName = kAppName;
